"""
新建一个data字典存放每位用户评论的电影和评分, 如果字典中没有某位用户，则使用用户ID来创建这位用户,否则直接添加以该用户ID为key字典中
2.1用户-电影评分矩阵的构建
我们从网络中获得到MovieLens数据集对用户-电影评分矩阵进行构建，
数据集中包含610个用户对不同电影的10万个评分，分值为1至5，不同的用户通过电影ID相互联系，如下表所示：
"""

# 2.2寻找近邻用户
# 对于不同用户之间的相似性，常常通过简单的欧式距离进行计算[2]，我们将采用person系数，也称为皮尔森积矩相关系数，
# 对不同用户间的相似性进行计算。对于不同的用户，person系数取值在[-1,1]之间，
# 如果person系数接近于1，则表示两个用户之间具有强相关性，两者对电影具有相同的个人喜好。
# 当person系数接近于-1时，两个用户间完全负相关，及表示两人对电影的喜好完全对立。
# Person系数为0时，表示两个用户对电影的个人喜好并没有太多的联系。计算公式如下：

"""
2.3电影推荐
针对我们将要推荐的用户，我们通过近邻用户间的评分进行预测

本文只提供了欧式距离的算法，如果需要，可更改Euclidean，用numpy函数内的pccs = np.corrcoef(x, y)进行替换，完整代码如下：
"""

from math import *

import pandas as pd

"""
读取movies文件，设置列名为’videoId', 'title', 'genres'
读取ratings文件，设置列名为'userId', 'videoId', 'rating', 'timestamp'
通过两数据框之间的 videoId 连接
保存'userId', 'rating', 'videoId', 'title'为data数据表
"""
movies = pd.read_csv("../ml-25m/movies.csv", names=['videoId', 'title', 'genres'])
ratings = pd.read_csv("../ml-25m/ratings.csv", names=['userId', 'videoId', 'rating', 'timestamp'])
data = pd.merge(movies, ratings, on='videoId')
data[['userId', 'rating', 'videoId', 'title']].sort_values('userId').to_csv('../../ml-25m/data.csv', index=False)

"""
新建一个data字典存放每位用户评论的电影和评分, 如果字典中没有某位用户，则使用用户ID来创建这位用户,否则直接添加以该用户ID为key字典中
"""
file = open("../ml-25m/data.csv", 'r', encoding='UTF-8')
data = {}
for line in file.readlines():
    line = line.strip().split(',')
    if not line[0] in data.keys():
        data[line[0]] = {line[3]: line[1]}
    else:
        data[line[0]][line[3]] = line[1]

"""
找到两位用户共同评论过的电影,然后计算两者之间的欧式距离，最后算出两者之间的相似度，欧式距离越小两者越相似
"""
def Euclidean(user1, user2):
    user1_data = data[user1]
    user2_data = data[user2]
    distance = 0
    for key in user1_data.keys():
        if key in user2_data.keys():
            distance += pow(float(user1_data[key]) - float(user2_data[key]), 2)
    return 1 / (1 + sqrt(distance))

"""
计算某个用户与其他用户的相似度
"""
def top_simliar(userID):
    res = []
    for userid in data.keys():
        # 排除与自己计算相似度
        if not userid == userID:
            simliar = Euclidean(userID, userid)
            res.append((userid, simliar))
    res.sort(key=lambda val: val[1])
    return res[:4]


"""
从控制台输入需要推荐的用户ID，如果用户不在原始数据集中则报错，重新输入
"""
getIdFlag = 0
while not getIdFlag:
    inputUid = str(input("请输入用户ID\n"))
    try:
        uid = data[inputUid]
        getIdFlag = 1
    except Exception:
        print("用户ID错误，请重新输入\n")

"""
根据与当前用户相似度最高的用户评分记录，按降序排列，推荐出改用户还未观看的评分最高的10部电影
"""
def recommend(user):
    top_sim_user = top_simliar(user)[0][0]
    items = data[top_sim_user]
    recommendations = []
    for item in items.keys():
        if item not in data[user].keys():
            recommendations.append((item, items[item]))
    recommendations.sort(key=lambda val: val[1], reverse=True)  # 按照评分排序
    return recommendations[:10]


"""
根据输入的用户ID，输出为他推荐的影片
"""
Recommendations = recommend(inputUid)
print("为用户" + inputUid + "推荐下列评分最高的十部影片\n")
for video in Recommendations:
    print(video)
